#include <fstream>
#include <iostream>
#include <cstring>
#include <CommandLineParameters.h>
#include "CompilerManager.h"
#include "WllSingleton.h"
#include "CompilerId.h"
#include <zmq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include "WllTrace.h"
#include "MatchMode.h"
using namespace std;

const static int MAX_BUFFER_LENGTH=4096;
const static string LOCAL_HOST = "127.0.0.1";

bool ProcessFile(const string &file_name, vector<Symbols>& output, CompilerManager* &compiler_manager, Compiler* &compiler, MatchMode match_mode = TOTAL_MODE)
{
    ifstream input(file_name.c_str());
    if(input.fail())
    {
        TERM_ERROR("open file ["<<file_name<<"] failed!");
        return false;
    }

    INFO("loading ["<<file_name<<"] ...");
    Compiler* sub_compiler = compiler_manager->GetCompiler(file_name);
    if (sub_compiler == nullptr)
    {
        compiler->processing_file_name = file_name;
        StreamSymbolsFlow streamSymbolsFlow(input);
        bool retval = compiler->Process(streamSymbolsFlow,output, compiler->languages.GetDefaultStartSymbol(), match_mode);
        if(!retval)
        {
            TERM_ERROR("process file ["<<file_name<<"] failed!");
            return false;
        }
        else
        {
            INFO("process file ["<<file_name<<"] success!");
        }
        sub_compiler = compiler->interpreter->context.compiler;

        if (sub_compiler != nullptr)
        {
            LanguageParsers* parser = compiler_manager->CreateLanguageParser(compiler_manager->language_parser_type, sub_compiler);
            parser->Initialize();
            sub_compiler->parser_strategy.Set(parser);

            sub_compiler->compiler_id = *compiler_manager->GetGrammarId(file_name);
            sub_compiler->compiler_id.parent_compiler_id = compiler->compiler_id.compiler_id;
            sub_compiler->languages.grammar_file_name = file_name;

            compiler_manager->CacheCompiler(sub_compiler);

            compiler = sub_compiler;
            compiler_manager->SetCurrentCompiler(compiler);
            INFO("["<<compiler->compiler_id.ToString()<<"] loaded.");
        }
    }
    else
    {
        compiler = sub_compiler;
        INFO("["<<compiler->compiler_id.ToString()<<"] loaded from cache.");
    }
    return true;
}

bool ProcessFiles(vector<string>& files, vector<Symbols>& input, vector<Symbols>& output, CompilerManager* &compiler_manager, Compiler* &compiler)
{
    MatchMode match_mode = TOTAL_MODE;

    for(int i=0; i<files.size(); i++)
    {
        string file_name = files[i];

        if (file_name.substr(0,1) == "-")
        {
            if (file_name == "-TOTAL")
            {
                match_mode = TOTAL_MODE;
            }
            else if (file_name == "-PART")
            {
                match_mode = PART_MODE;
            }
            continue;
        }

        if(!ProcessFile(file_name, output, compiler_manager, compiler, match_mode))
        {
            return false;
        }
    }
    return compiler->Process(input, output, match_mode);
}

bool ProcessFiles(vector<string>& files, vector<Symbols>& output, CompilerManager* &compiler_manager, Compiler* &compiler)
{
    MatchMode match_mode = TOTAL_MODE;

    for(int i=0; i<files.size(); i++)
    {
        string file_name = files[i];

        if (file_name.substr(0,1) == "-")
        {
            if (file_name == "-TOTAL")
            {
                match_mode = TOTAL_MODE;
            }
            else if (file_name == "-PART")
            {
                match_mode = PART_MODE;
            }
            continue;
        }

        if(!ProcessFile(file_name, output, compiler_manager, compiler, match_mode))
        {
            return false;
        }
    }
    return true;
}

void SplitPipelineFileGroups(CommandLineParameters& commandLineParameters, vector<vector<string>>& pipeline_file_groups)
{
    vector<string>& params = commandLineParameters.GetParameters();
    if (!params.empty())
    {
        pipeline_file_groups.push_back(vector<string>());
        for (vector<string>::const_iterator i = params.begin(); i != params.end(); ++i)
        {
            if (*i == "=")
            {
                pipeline_file_groups.push_back(vector<string>());
            }
            else
            {
                pipeline_file_groups.back().push_back(*i);
            }
        }
    }
}

//TODO: 减少CompilerManager* &compiler_manager, Compiler* &compiler参数传递
bool ProcessCommand(CommandLineParameters& commandLineParameters, vector<Symbols>& output, CompilerManager* &compiler_manager, Compiler* &compiler)
{
    vector<vector<string>> pipeline_file_groups;
    SplitPipelineFileGroups(commandLineParameters, pipeline_file_groups);
    if (pipeline_file_groups.empty())
    {
        return false;
    }
    if (!ProcessFiles(pipeline_file_groups[0], output, compiler_manager, compiler))
    {
        return false;
    }
    for (int i = 1; i < pipeline_file_groups.size(); ++i)
    {
        vector<Symbols> input(output);
        output.clear();
        compiler = compiler_manager->CreateOrGetBoostrapCompiler();
        if (!ProcessFiles(pipeline_file_groups[i], input, output, compiler_manager, compiler))
        {
            return false;
        }
    }
    return true;
}

bool ProcessCommand(CommandLineParameters& commandLineParameters, ostream &result, CompilerManager* &compiler_manager, Compiler* &compiler)
{
    vector<Symbols> output;
    bool retval = ProcessCommand(commandLineParameters, output, compiler_manager, compiler);
    result << output;
    return retval;
}


int main(int argc,char** argv)
{
    CommandLineParameters commandLineParameters(argc,argv);
    string mode = commandLineParameters.GetOption("mode");
    if (mode == "")
    {
        mode = "local";
    }
    string host = commandLineParameters.GetOption("host");
    if (host == "")
    {
        host = LOCAL_HOST;
    }
    string port = commandLineParameters.GetOption("port");
    if (port == "")
    {
        port = "12345";
    }
    string connect = commandLineParameters.GetOption("connect");
    if (connect == "")
    {
        connect += "tcp://";
        connect += host + ":" + port;
    }

    if (mode == "server")
    {
        CompilerManager* compiler_manager = Singleton<CompilerManager>::GetInstance();
        Compiler* compiler = compiler_manager->CreateOrGetBoostrapCompiler();
        compiler_manager->SetCurrentCompiler(compiler);

        //  Socket to talk to clients
        void *context = zmq_ctx_new ();
        void *responder = zmq_socket (context, ZMQ_REP);
        int rc = zmq_bind (responder, connect.c_str());
        assert (rc == 0);

        while (true) {
            char buffer [MAX_BUFFER_LENGTH];
            memset(buffer,0,MAX_BUFFER_LENGTH);
            zmq_recv (responder, buffer, MAX_BUFFER_LENGTH, 0);
            INFO("Received command:" << buffer);

            if (strncmp(buffer,"cwd:",4)==0)
            {
                chdir(buffer+4);
                zmq_send(responder, buffer, MAX_BUFFER_LENGTH, 0);
            }
            else
            {
                CommandLineParameters command(buffer);
                stringstream result;
                char retval = !ProcessCommand(command, result, compiler_manager, compiler);
                zmq_send(responder, &retval, 1, 0);

                char sync;
                zmq_recv(responder, &sync, 1, 0);

                strncpy(buffer, result.str().c_str(), MAX_BUFFER_LENGTH);
                buffer[MAX_BUFFER_LENGTH-1] = 0;
                zmq_send(responder, buffer, MAX_BUFFER_LENGTH, 0);
            }
        }
    }
    else if (mode == "local") /* 本地模式 */
    {
        if(argc==1) /* 本地交互模式 */
        {
            CompilerManager* compiler_manager = Singleton<CompilerManager>::GetInstance();
            Compiler* compiler = compiler_manager->CreateOrGetBoostrapCompiler();
            compiler_manager->SetCurrentCompiler(compiler);

            //cout<<argv[0]<<"<grammar_file> <input_file>"<<endl;
            TERM_INFO("WLL0 language auto loaded ...");
            MatchMode match_mode = TOTAL_MODE;
            do
            {
                string cmd;
                cin>>cmd;
                if(cmd=="quit")
                {
                    break;
                }
                if(cmd=="load")
                {
                    string file_name;
                    cin>>file_name;
                    vector<Symbols> output;
                    if(ProcessFile(file_name, output, compiler_manager, compiler, match_mode))
                    {
                        TERM_INFO(file_name << " load success!");
                        cout << output << endl;
                    }
                    else
                    {
                        TERM_ERROR(file_name << " load failed!!!");
                    }
                }
                else if(cmd=="grammar")
                {
                    TERM_INFO("current loaded language rules:");
                    compiler->DisplayLanguage(cout);
                }
                else if(cmd=="dump")
                {
                    string file_name;
                    cin>>file_name;
                    compiler->DumpLanguage(file_name);
                    TERM_INFO("["<<file_name<<"] dumped.");
                }
                else if (cmd == "debug")
                {
                    compiler->DisplayLanguage(cerr);
                    if (compiler->parser_strategy.Get()->Initialize())
                    {
                        compiler->parser_strategy.Get()->Show();
                    }
                }
                else if (cmd == "match")
                {
                    string match_mode_str;
                    cin >> match_mode_str;
                    if (match_mode_str == "TOTAL")
                    {
                        match_mode = TOTAL_MODE;
                        cout << "match mode switch to TOTAL_MODE" << endl;
                    }
                    else if (match_mode_str == "PART")
                    {
                        match_mode = PART_MODE;
                        cout << "match mode switch to PART_MODE" << endl;
                    }
                    else
                    {
                        cout << "unknown match mode, invalid = (TOTAL|PART)" << endl;
                    }
                }
                else
                {
                    TERM_ERROR("unknown command!!!");
                }
            }while(true);

            return 0;
        }
        else /* 本地批处理模式 */
        {
            CompilerManager* compiler_manager = Singleton<CompilerManager>::GetInstance();
            Compiler* compiler = compiler_manager->CreateOrGetBoostrapCompiler();
            compiler_manager->SetCurrentCompiler(compiler);

            return !ProcessCommand(commandLineParameters, cout, compiler_manager, compiler);
        }
    }
    else if (mode == "client")
    {
            void *context = zmq_ctx_new ();
            void *requester = zmq_socket (context, ZMQ_REQ);
            zmq_connect (requester, connect.c_str());

            char buffer[MAX_BUFFER_LENGTH];
            if (host == LOCAL_HOST)
            {
                memset(buffer, 0, MAX_BUFFER_LENGTH);
                strcpy(buffer, "cwd:");
                getcwd(buffer + 4, MAX_BUFFER_LENGTH);
                buffer[MAX_BUFFER_LENGTH-1] = 0;
                zmq_send(requester, buffer, strlen(buffer), 0);
                memset(buffer, 0, MAX_BUFFER_LENGTH);
                zmq_recv(requester, buffer, MAX_BUFFER_LENGTH, 0);
		        buffer[MAX_BUFFER_LENGTH-1]=0;
                INFO(buffer);
            }

            string command_line = commandLineParameters.ToString();
            zmq_send(requester, command_line.c_str(), command_line.size(), 0);

            char retval = 0;
            zmq_recv(requester, buffer, 1, 0);
            retval = buffer[0];

            char sync;
            zmq_send(requester, &sync, 1, 0);

            memset(buffer, 0, MAX_BUFFER_LENGTH);
            zmq_recv(requester, buffer, MAX_BUFFER_LENGTH, 0);
            buffer[MAX_BUFFER_LENGTH-1]=0;
            cout << buffer << endl;

            zmq_close (requester);
            zmq_ctx_destroy (context);
            return retval;
    }
    else
    {
        TERM_ERROR("unknown mode " << mode);
    }

	return 0;
}

